package com.tianyu.inspection.service.telemetry.yukong;

import com.tianyu.inspection.common.Constants;
import com.tianyu.inspection.common.HttpRequest;
import com.tianyu.inspection.common.component.AppConstants;
import com.tianyu.inspection.common.model.JedisModel;
import com.tianyu.inspection.mapper.FlyTaskMapper;
import com.tianyu.inspection.mapper.PipelineMapper;
import com.tianyu.inspection.mapper.UploadFileMapper;
import com.tianyu.inspection.service.api.IProjectService;
import com.tianyu.inspection.service.telemetry.yukong.tcp.YkDecoder;
import com.tianyu.inspection.service.telemetry.yukong.tcp.YkEncoder;
import com.tianyu.inspection.service.telemetry.yukong.tcp.YkTcpClientChannelHandler;
import com.tianyu.inspection.service.websocket.WebSocketService;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.timeout.IdleStateHandler;
import io.ocs.model.OcsSimpleModel;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ui.ModelMap;

import java.net.InetSocketAddress;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**********************************
 * @Author YSW
 * @Description
 * @Date 2021/3/23 - 17:07
 **********************************/

@Slf4j
public class YkClient {

    /**
     * 类静态MAP [项目ID -> Netty Channel]
     */
    public static final Map<Integer, Channel> YK_CHANNEL_MAP = new ConcurrentHashMap<>();

    /**
     * 类静态MAP [项目ID -> 御空登录信息]
     */
    public static final Map<Integer, String> YK_PROJECT_ID_TOKEN_MAP = new ConcurrentHashMap<>();

    private final Integer projectId;
    private final String ykAccessKey;
    private final String ykAccessSecret;
    private final WebSocketService webSocketService;
    private final FlyTaskMapper flyTaskMapper;
    private final PipelineMapper pipelineMapper;
    private final UploadFileMapper uploadFileMapper;
    private final IProjectService projectService;
    private final OcsSimpleModel ocsSimpleModel;
    private final JedisModel jedisModel;
    private final AppConstants appConstants;
    private final EventLoopGroup group = new NioEventLoopGroup();
    private String token;

    public YkClient(Boolean isReconnected, Integer projectId, String ykAccessKey,
                    String ykAccessSecret, WebSocketService webSocketService, FlyTaskMapper flyTaskMapper, PipelineMapper pipelineMapper, UploadFileMapper uploadFileMapper, IProjectService projectService,
                    OcsSimpleModel ocsSimpleModel, JedisModel jedisModel, AppConstants appConstants) {
        this.projectId = projectId;
        this.ykAccessKey = ykAccessKey;
        this.ykAccessSecret = ykAccessSecret;
        this.webSocketService = webSocketService;
        this.flyTaskMapper = flyTaskMapper;
        this.pipelineMapper = pipelineMapper;
        this.uploadFileMapper = uploadFileMapper;
        this.projectService = projectService;
        this.ocsSimpleModel = ocsSimpleModel;
        this.jedisModel = jedisModel;
        this.appConstants = appConstants;
        ModelMap login = HttpRequest.yuKongThirdLogin(appConstants.yuKongHttpHost, ykAccessKey, ykAccessSecret);
        if (Objects.isNull(login)) {
            log.error("Project[" + projectId + "] ykAccessKey[" + ykAccessKey + "],ykAccessSecret[" + ykAccessSecret + "] login unsuccessfully! ");
            log.error("HTTP 连接未成功, 2S后重连...");
            new Timer().schedule(new TimerTask() {
                @Override
                public void run() {
                    Runnable runnable = () -> new YkClient(
                            true,
                            projectId,
                            ykAccessKey,
                            ykAccessSecret,
                            webSocketService,
                            flyTaskMapper,
                            pipelineMapper,
                            uploadFileMapper,
                            projectService,
                            ocsSimpleModel,
                            jedisModel,
                            appConstants);
                    Constants.EXECUTOR.execute(runnable);
                    log.info("project[" + projectId + "] do reconnect");
                }
            }, 2000);
        } else if (login.get("state").equals(1)) {
            token = (String) login.get("token");
            YK_PROJECT_ID_TOKEN_MAP.put(projectId, token);
            ModelMap userInfo = HttpRequest.getUserInfoFromYuKong(appConstants.yuKongHttpHost, token);
            if (Objects.nonNull(userInfo)) {
                String format = "Project[{}] 御空企业账户[{}] " + (isReconnected ? "Re-" : "") + "Login Successfully! ";
                log.info(format, projectId, userInfo.get("id"));
                log.info("token: {}", token);
                final String key = "yukong-third:" + userInfo.get("id");
                jedisModel.hset(key, "projectId", String.valueOf(projectId));
                jedisModel.hset(key, "ykAccessKey", ykAccessKey);
                jedisModel.hset(key, "ykAccessSecret", ykAccessSecret);
                jedisModel.hset(key, "token", token);
                jedisModel.hset(key, "timestamp", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date()) + "");
            }
            Channel channel = newTcpClient();
            YK_CHANNEL_MAP.put(projectId, channel);
        } else {
            throw new IllegalStateException("登录异常...");
        }
    }

    public Channel newTcpClient() {
        Bootstrap bootstrap = new Bootstrap();
        bootstrap.group(group)
                .option(ChannelOption.TCP_NODELAY, true)
                .channel(NioSocketChannel.class)
                .handler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel socketChannel) {
                        socketChannel.pipeline().addLast("encoder", new YkEncoder());
                        socketChannel.pipeline().addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 3,
                                4, 0, 2, true));
                        socketChannel.pipeline().addLast("decoder", new YkDecoder());
                        socketChannel.pipeline().addLast(new IdleStateHandler(3, 3, 3));
                        socketChannel.pipeline().addLast(new YkTcpClientChannelHandler(projectId, token, jedisModel, ykAccessKey, ykAccessSecret, webSocketService, flyTaskMapper, pipelineMapper,
                                uploadFileMapper,
                                projectService,
                                ocsSimpleModel,
                                appConstants));
                    }
                });
        Channel channel = null;
        try {
            ChannelFuture future = bootstrap.connect(new InetSocketAddress(appConstants.yuKongTcpServerHost, appConstants.yuKongTcpServerPort)).sync();
            channel = future.channel();
        } catch (InterruptedException e) {
            e.printStackTrace();
            log.info("TCP 连接未成功, 2S后重连...");
            new Timer().schedule(new TimerTask() {
                @Override
                public void run() {
                    newTcpClient();
                    log.info("project[" + projectId + "] do reconnect");
                }
            }, 2000);
        }
        return channel;
    }

    @Data
    @AllArgsConstructor
    public static class YkLoginMessage {
        private String userId;
        private String token;
        private String cpAccessKey;
        private String cpAccessSecret;
    }
}
